﻿
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace GFramework.Netwrok
{
    public class TCPNetService : INetService
    {
        public bool Connected
        {
            get
            {
                if(_socket == null)
                {
                    return false;
                }
                return _socket.Connected;
            }
        }

        public byte NetType
        {
            get
            {
                return NetTypeCode.TCP;
            }
        }

        public event GAction<INetService, INetService> NetServiceAccepted;

        public event GAction<INetService, object> NetServiceConnected;

        public event GAction<INetService> NetServiceClosed;

        public event GAction<INetService> NetServiceSended;

        public event GAction<INetService> NetServiceReceived;

        public event GAction<INetService, NetErrorCode, int, string> NetServiceError;

        private SocketAsyncEventArgs _acceptArgs;
        private Socket _acceptSocket;

        private SocketAsyncEventArgs _innArgs;
        private SocketAsyncEventArgs _outArgs;
        private Socket _socket;

        public TCPNetService()
        {

        }

        public TCPNetService(Socket socket)
        {
            _socket?.Shutdown(SocketShutdown.Both);
            _socket?.Close();
            _socket = socket;
            _socket.NoDelay = true;
            if (_innArgs == null)
            {
                _innArgs = new SocketAsyncEventArgs();
                _innArgs.Completed += OnComplete;
            }
            if (_outArgs == null)
            {
                _outArgs = new SocketAsyncEventArgs();
                _outArgs.Completed += OnComplete;
            }
        }

        #region Accept
        public void Accept(string address)
        {
            IPEndPoint ipEndPoint = NetHelper.DomainToIPEndPoint(address);
            _acceptSocket?.Shutdown(SocketShutdown.Both);
            _acceptSocket?.Disconnect(true);

            if(_acceptSocket == null)
            {
                _acceptSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                _acceptSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            }
            if (_acceptArgs == null)
            {
                _acceptArgs = new SocketAsyncEventArgs();
                _acceptArgs.Completed += OnComplete;
            }

            _acceptSocket.Bind(ipEndPoint);
            _acceptSocket.Listen(ipEndPoint.Port);
            AcceptAsync();
        }

        public void AcceptAsync()
        {
            _acceptArgs.AcceptSocket = null;
            if (_acceptSocket.AcceptAsync(_acceptArgs))
            {
                return;
            }
            OnAcceptComplete(_acceptArgs);
        }

        private void OnAcceptComplete(SocketAsyncEventArgs e)
        {
            if(e.SocketError == SocketError.Success)
            {
                NetServiceAccepted?.Invoke(this, new TCPNetService(e.AcceptSocket));
            }
            else
            {
                NetServiceError?.Invoke(this, NetErrorCode.AcceptError, (int)e.SocketError, "accept error!");
            }
        }
        #endregion

        #region Connect
        public void Connect(string address)
        {
            IPEndPoint ipEndPoint = NetHelper.DomainToIPEndPoint(address);
            _socket?.Shutdown(SocketShutdown.Both);
            _socket?.Disconnect(true);
            if (_socket == null)
            {
                _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                _socket.NoDelay = true;
            }
            if (_innArgs == null)
            {
                _innArgs = new SocketAsyncEventArgs();
                _innArgs.Completed += OnComplete;
            }
            if (_outArgs == null)
            {
                _outArgs = new SocketAsyncEventArgs();
                _outArgs.Completed += OnComplete;
            }
            ConnectAsync(ipEndPoint);
        }

        public void Connect(string address, object userData)
        {
            IPEndPoint ipEndPoint = NetHelper.DomainToIPEndPoint(address);
            _socket?.Disconnect(true);
            if (_socket == null)
            {
                _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                _socket.NoDelay = true;
            }
            if (_innArgs == null)
            {
                _innArgs = new SocketAsyncEventArgs();
                _innArgs.Completed += OnComplete;
            }
            if (_outArgs == null)
            {
                _outArgs = new SocketAsyncEventArgs();
                _outArgs.Completed += OnComplete;
            }
            _outArgs.UserToken = userData;
            ConnectAsync(ipEndPoint);
        }

        public void ConnectAsync(IPEndPoint ipEndPoint)
        {
            _outArgs.RemoteEndPoint = ipEndPoint;
            if (_socket.ConnectAsync(_outArgs))
            {
                return;
            }
            OnConnectComplete(_outArgs);
        }

        private void OnConnectComplete(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                NetServiceConnected?.Invoke(this, e.UserToken);
            }
            else
            {
                NetServiceError?.Invoke(this, NetErrorCode.ConnectError, (int)e.SocketError, "connect error!");
            }
        }

        private void OnDisconnectComplete(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                NetServiceClosed?.Invoke(this);
            }
            else
            {
                NetServiceError?.Invoke(this, NetErrorCode.ConnectError, (int)e.SocketError, "disconnect error!");
            }
        }
        #endregion

        #region Send

        public void Send(MemoryStream stream)
        {
            try
            {
                _outArgs.SetBuffer(stream.GetBuffer(), (int)stream.Position, (int)(stream.Length - stream.Position));
            }
            catch (Exception e)
            {
                throw new GException($"socket set buffer error: {stream.Length}, {stream.Position}, {stream.Length - stream.Position}", e);
            }
            if (_socket.SendAsync(_outArgs))
            {
                return;
            }
            OnSendComplete(_outArgs);
        }

        private void OnSendComplete(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                NetServiceSended?.Invoke(this);
            }
            else
            {
                NetServiceError?.Invoke(this, NetErrorCode.SendError, (int)e.SocketError, "send error!");
            }
        }
        #endregion

        #region Receive
        public void Receive(MemoryStream stream)
        {
            try
            {
                _innArgs.SetBuffer(stream.GetBuffer(), (int)stream.Position, (int)(stream.Length - stream.Position));
            }
            catch (Exception e)
            {
                throw new GException($"socket set buffer error: {stream.Length}, {stream.Position}, {stream.Length - stream.Position}", e);
            }

            if (_socket.ReceiveAsync(_innArgs))
            {
                return;
            }
            OnRecvComplete(_innArgs);
        }

        private void OnRecvComplete(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                NetServiceReceived?.Invoke(this);
            }
            else
            {
                NetServiceError?.Invoke(this, NetErrorCode.ReceiveError, (int)e.SocketError, "receive error!");
            }
        }
        #endregion

        public void Close()
        {
            _acceptSocket?.Close();
            _socket?.Close();
        }

        private void OnComplete(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Accept:
                    OnAcceptComplete(e);
                    break;
                case SocketAsyncOperation.Connect:
                    OnConnectComplete(e);
                    break;
                case SocketAsyncOperation.Receive:
                    OnRecvComplete(e);
                    break;
                case SocketAsyncOperation.Send:
                    OnSendComplete(e);
                    break;
                case SocketAsyncOperation.Disconnect:
                    OnDisconnectComplete(e);
                    break;
                default:
                    throw new GException($"socket error: {e.LastOperation}");
            }
        }

        public void Clear()
        {
            _acceptSocket?.Shutdown(SocketShutdown.Both);
            _acceptSocket?.Close();
            _acceptSocket = null;
            _acceptArgs?.Dispose();
            _socket?.Shutdown(SocketShutdown.Both);
            _socket?.Close();
            _socket = null;
            _innArgs?.Dispose();
            _outArgs?.Dispose();
            NetServiceAccepted = null;
            NetServiceConnected = null;
            NetServiceClosed = null;
            NetServiceSended = null;
            NetServiceReceived = null;
        }
    }
}
